package com.wt.springsamples.security.config;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration implements BeanFactoryAware {


    protected BeanFactory context;


    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public MethodSecurityMetadataSource methodSecurityMetadataSource() {
        List<MethodSecurityMetadataSource> sources = new ArrayList<>();
        ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
                getExpressionHandler());
        MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
        if (customMethodSecurityMetadataSource != null) {
            sources.add(customMethodSecurityMetadataSource);
        }
        boolean hasCustom = customMethodSecurityMetadataSource != null;
        boolean isPrePostEnabled = prePostEnabled();
        boolean isSecuredEnabled = securedEnabled();
        boolean isJsr250Enabled = jsr250Enabled();
        Assert.state(isPrePostEnabled || isSecuredEnabled || isJsr250Enabled || hasCustom,
                "In the composition of all global method configuration, "
                        + "no annotation support was actually activated");
        if (isPrePostEnabled) {
            sources.add(new MyPrePostAnnotationSecurityMetadataSource(attributeFactory));
        }
        if (isSecuredEnabled) {
            sources.add(new SecuredAnnotationSecurityMetadataSource());
        }
        if (isJsr250Enabled) {
            GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
            Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context
                    .getBean(Jsr250MethodSecurityMetadataSource.class);
            if (grantedAuthorityDefaults != null) {
                jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
            }
            sources.add(jsr250MethodSecurityMetadataSource);
        }
        return new DelegatingMethodSecurityMetadataSource(sources);
    }

    protected <T> T getSingleBeanOrNull(Class<T> type) {
        try {
            return this.context.getBean(type);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
        return null;
    }

    protected boolean prePostEnabled() {
        return enableMethodSecurity().getBoolean("prePostEnabled");
    }

    protected boolean securedEnabled() {
        return enableMethodSecurity().getBoolean("securedEnabled");
    }

    protected boolean jsr250Enabled() {
        return enableMethodSecurity().getBoolean("jsr250Enabled");
    }

    protected boolean isAspectJ() {
        return enableMethodSecurity().getEnum("mode") == AdviceMode.ASPECTJ;
    }

    private AnnotationAttributes enableMethodSecurity;
    private AnnotationAttributes enableMethodSecurity() {
        if (this.enableMethodSecurity == null) {
            // if it is null look at this instance (i.e. a subclass was used)
            EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils.findAnnotation(getClass(),
                    EnableGlobalMethodSecurity.class);
            Assert.notNull(methodSecurityAnnotation, () -> EnableGlobalMethodSecurity.class.getName() + " is required");
            Map<String, Object> methodSecurityAttrs = AnnotationUtils.getAnnotationAttributes(methodSecurityAnnotation);
            this.enableMethodSecurity = AnnotationAttributes.fromMap(methodSecurityAttrs);
        }
        return this.enableMethodSecurity;
    }
}
